Day 3 發現 PPG-DaLiA 的峰間期全是 15.625 ms 的整數倍:64 Hz 的 PPG,峰只能落在每 15.625 ms 一個的格點上。我當時寫了「上游的取樣率是下游指標的精度上限」,但沒有驗證。
但64 Hz 是量測那一瞬間照 64 次嗎?同一份資料,腕上 PPG 是 64 Hz,胸前 ECG 是 700 Hz,差了十倍。64 Hz 算心率夠嗎?算 HRV 呢?今天把同一段 PPG 降到更低的取樣率,看哪個指標先壞。
每秒 64 個等距的點,間隔 1/64 秒=15.625 ms,不是一瞬間連拍 64 次。安靜時一拍約 0.8–1 秒,大約是 51–64 個點。
但對 HRV 重要的不是一拍有幾個點,而是峰只能落在格點上:峰間期一定是 15.625 ms 的整數倍,心跳快慢都一樣。
我猜 RMSSD 最先壞,SDNN 其次,HR 最後。
HR 最耐。一段時間的平均間期=(最後一峰 − 第一峰)÷ 間期數,中間每個峰的誤差一加一減會抵消,只剩頭尾兩個。25 Hz 下頭尾各最多偏 20 ms,兩峰相距 7 秒的話,平均心率最多差約 0.6%。所以低取樣率下 HR 壞掉的方式是數錯拍,不是峰的位置不準。
RMSSD 最脆弱,原因在「相鄰相減」:一個峰偏了,前一個間期變長、後一個變短,兩個再相減,誤差變兩倍。假設誤差是四捨五入到格點、彼此獨立,取樣週期 T=1/fs,RMSSD² 會多出 T²/2,SDNN² 只多出 T²/6。而且 RMSSD 本來就比較小:Day 4 的 nsr001,5 分鐘視窗的 RMSSD 中位數 25.09 ms、SDNN 49.66 ms,而 25 Hz 的格點是 40 ms。
由此還能推出兩件事:RMSSD 只會往上偏;偏多少不是常數,本來 RMSSD 就低的人偏得更多。這是理想化的預測,以實驗結果為準。
decimate: 先低通濾波再抽點,只能整數倍(64 → 32、16、8 Hz)。scipy 預設正反各濾一次(zero_phase=True),不會把峰往後推。resample/resample_poly: 也會先抗混疊,比例不必是整數,例如 64 → 25 Hz。resample 用 FFT,假設訊號頭尾相接,邊界容易出假波紋。取樣率越高,LED 打光和讀取的次數越多,要算、要存的資料也越多,代價是電量。各家實際用多少 Hz,我沒找到公開來源,這裡不寫數字。而且拉高取樣率救不了白天最大的誤差:動作。這也是多數廠商選在睡眠時算 HRV 的原因(Day 5)。
不能。低取樣率的 RMSSD 偏高,而且因人而異,減一個常數校正不了。
這件事不該丟給 LLM 自己判斷,而是由 pipeline 用規則擋下:時間解析度不同的數值,不放進同一條 baseline、不互相比較。時間解析度要看取樣率和峰怎麼定位(有沒有內插),這兩項接在 Day 4 列的 source、window_s、beat_filter、ddof 後面。同一台裝置也會變:廠商的演算法可能不定期更新(Dial 2025)。如果更新動到取樣率,baseline 會斷在那一天,模型只看到「這週 HRV 變了」,很容易說成壓力或疲勞。
兩份資料,角色不同:
jittered_rr 產生 600 個間期,平均 900 ms,標準差 5、10、20、40 ms 四組(seed=6),真值 RMSSD 是 6.82、13.63、27.27、54.53 ms。synthetic_ppg_from_rr 把它畫成 64 Hz 的波形,同時回傳 601 個峰的連續時間。峰在哪一刻是已知的,誤差可以直接算。naive)、decimate、resample_poly、resample。64 → 25 Hz 不是整數倍,只有後兩種能做。真實資料先對整段靜坐段降採樣再切窗,邊界效應只落在整段頭尾。find_pulse_peaks 找峰:峰時間就是最大樣本的位置,沒有內插。再用 match_peaks 和參照一對一配對(容許 0.2 秒,時間差小的先配,避免 Day 3 配到隔壁拍的錯)。配到的峰中間有洞的窗不算指標。import numpy as np
from wearable_ai.hrv import rmssd
from wearable_ai.signal_processing import find_pulse_peaks
from wearable_ai.signal_processing.matching import match_peaks
from wearable_ai.signal_processing.sampling import downsample
from wearable_ai.synthetic import jittered_rr, synthetic_ppg_from_rr
rr = jittered_rr(600, 900.0, 5.0, seed=6)
x, truth_s = synthetic_ppg_from_rr(rr, 64.0) # 64 Hz 波形、601 個峰的連續時間(秒)
y = downsample(x, 64.0, 16.0, "resample_poly")
peaks_s = find_pulse_peaks(y, 16.0) / 16.0 # 最大樣本的位置,沒有內插
ref_idx, test_idx = match_peaks(truth_s, peaks_s, tol_s=0.2) # 601 對
rmssd(np.diff(truth_s) * 1000) # 6.82
rmssd(np.diff(peaks_s[test_idx]) * 1000) # 55.71
完整實驗在 days/day06/sampling_rate.py,加 --plot 輸出下面四張圖。

合成 PPG 的 HR、SDNN、RMSSD 相對真值的誤差對取樣率;HR 都在 0.02% 以內,RMSSD 真值 6.8 ms 那條在 8 Hz 到 +1,055%
| 取樣率 | T/4(ms) | 峰時間誤差中位數(ms) | RMSSD,真值 6.82 | 13.63 | 27.27 | 54.53 |
|---|---|---|---|---|---|---|
| 64 Hz | 3.91 | 3.62–4.07 | 13.39 | 17.66 | 29.31 | 54.89 |
| 32 Hz | 7.81 | 7.92–8.30 | 21.10 | 25.57 | 34.00 | 59.47 |
| 25 Hz | 10.00 | 9.02–10.25 | 36.65 | 34.36 | 37.45 | 60.96 |
| 16 Hz | 15.63 | 15.55–15.76 | 55.71 | 53.75 | 53.14 | 72.05 |
| 8 Hz | 31.25 | 31.31–31.85 | 78.79 | 78.79 | 83.30 | 99.82 |
64 Hz 是直接取樣,其餘用 resample_poly 降採樣。
days/day06/results.md 第 7 節)。閘門只看峰數,多一個假峰、漏一個真峰照樣過關。保留的 22 窗心率 43–65 bpm,其中 5 窗的 64 Hz 最短峰間期只有 265.62–453.12 ms(同窗 ECG 最短 R-R 是 791.43–1041.43 ms)。64 Hz PPG 的 RMSSD 因此是同窗 ECG 的 1.40 倍(S1 中位數)與 3.65 倍(S2)。

PPG-DaLiA 兩位受試者,降採樣後 HR、SDNN、RMSSD 相對 64 Hz 的誤差中位數對取樣率;32、25 Hz 接近 0,8 Hz 升到 5–26%
以這個參照算相對誤差,RMSSD 在 32、25、16、8 Hz 只偏 +0.26%、+0.60%、+3.17%、+14.51%(resample_poly,22 窗的中位數),HR 都在 0.06% 以內。數字小,是因為分母已經被灌大。所以另外比「平方的增量」:誤差彼此獨立時,它和參照的大小無關,可以直接對理論值 (T² − T₆₄²)/2 與 /6。

RMSSD² 與 SDNN² 比 64 Hz 多出的量對取樣率,虛線是理論值;8、16 Hz 兩位受試者都在虛線附近,32、25 Hz 時 S2 高於虛線、S1 是負值
resample_poly,22 窗合計);32、25 Hz 是 −1.28、0.32 倍,標準誤 934、1,386 ms²,比理論值 366、678 ms² 還大。
S1 靜坐段 60–66 秒,16 Hz decimate、8 Hz decimate、8 Hz naive 的樣本與找到的峰,灰色虛線是 64 Hz 找到的峰
8 Hz 時,naive 在 61.8 秒那一拍早了 140.62 ms;decimate 在 60.2、65.8 秒各晚 78.12 ms,超過兩次四捨五入的上限 70.31 ms。兩者只差在有沒有濾波,多出來的偏移來自濾波後的波形。四種做法到 16 Hz 以下才分歧,方向也不一致:8 Hz 配對有洞的窗 naive 8、decimate 5、resample_poly 8,22 窗分不出優劣。
今天最該帶走的不是「幾 Hz 才夠」,而是誤差要跟誰比。以 64 Hz 為參照,32 Hz 的 RMSSD 只差 +0.26%,看起來能放心降採樣;但參照本身是同窗 ECG 的 1.40–3.65 倍。之後評估 LLM 也有同樣的陷阱:拿一份本身有錯的參考答案算一致率,分數會好看。所以報告誤差之前,先量參照離可信來源多遠;分母不可信時,改用不隨分母縮放的量(今天是平方的增量),或換一個參照(Day 9 用 ECG)。
第二,解析度造成的偏差不是能事後扣掉的常數。平方的增量平均是 T²/2,但要很多拍平均才看得到:單一 30 秒窗的方向可能相反;變異比格點小時,實測是理論的 0.77–1.62 倍;一個峰跳 140 ms,就讓 21 窗的平均翻號。所以 Concept 第 5 節說的「由 pipeline 用規則擋下」,除了取樣率與峰怎麼定位,還要看得到每個數字是幾拍算出來的。
第三,下游看到的「變化」要先大過解析度本身的起伏。同一段真值 6.82 ms 的波形,64 Hz 量到 13.39 ms,降到 25 Hz 量到 36.65 ms。LLM 只看到「RMSSD 從 13 變 37」,很容易說成恢復變好,其實只是格點變粗。Day 17 做 deviation detection 時,門檻要先把這一層算進去。